package org.andengine.util.adt.bit;

/**
 * (c) 2013 Nicolas Gramlich
 *
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @author ngramlich
 * @since 18:14:12 - 02.03.2013
 */
public abstract class BitVector implements IBitVector {
    // ===========================================================
    // Constants
    // ===========================================================

    public static final int TRUE = 1;
    public static final int FALSE = 0;

    // ===========================================================
    // Fields
    // ===========================================================

    // ===========================================================
    // Constructors
    // ===========================================================

    public static IBitVector wrap(final byte[] pData) {
        return new ByteBackedBitVector(pData);
    }

    public static IBitVector wrap(final int pSize, final byte[] pData) {
        return new ByteBackedBitVector(pData);
    }

    public static IBitVector wrap(final long[] pData) {
        return new LongBackedBitVector(pData);
    }

    public static IBitVector wrap(final int pSize, final long[] pData) {
        return new LongBackedBitVector(pSize, pData);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    // ===========================================================
    // Methods
    // ===========================================================

    public static int getByteIndex(final int pIndex) {
        return pIndex / Byte.SIZE;
    }

    public static int getLongIndex(final int pIndex) {
        return pIndex / Long.SIZE;
    }

    public static int getIndexInByte(final int pIndex) {
        return pIndex % Byte.SIZE;
    }

    public static int getIndexInLong(final int pIndex) {
        return pIndex % Long.SIZE;
    }

    public static int getBitInByte(final byte pByte, final int pIndex) throws IllegalArgumentException {
        return BitVector.getBitsInByte(pByte, pIndex, 1);
    }

    public static int getBitsInByte(final byte pByte, final int pIndex, final int pCount) throws IllegalArgumentException {
        if ((pIndex < 0) || ((pIndex + pCount) > Byte.SIZE)) {
            throw new IllegalArgumentException("pIndex out of bounds: " + pIndex);
        }

        if ((pCount < 0) || (pCount > Byte.SIZE)) {
            throw new IllegalArgumentException("pBitCount out of bounds: " + pCount);
        }

        final int shift = Byte.SIZE - pIndex - pCount;
        final int shiftedByte = ((pByte & 0xFF) >> shift);

        final int mask = (0x01 << (pCount)) - 1;

        final int result = shiftedByte & mask;

        return result;
    }

    public static byte setBitInByte(final byte pByte, final int pIndex, final boolean pTrue) throws IllegalArgumentException {
        if ((pIndex < 0) || (pIndex >= Byte.SIZE)) {
            throw new IllegalArgumentException("pIndex out of bounds: " + pIndex);
        }

        if (pTrue) {
            final int mask = (0x80 >> pIndex);

            final byte result = (byte) (pByte | mask);

            return result;
        } else {
            final int mask = 0xFF ^ (0x80 >> pIndex);

            final byte result = (byte) (pByte & mask);

            return result;
        }
    }

    public static byte setBitsInByte(final byte pByte, final int pIndex, final byte pBits, final int pBitIndex, final int pBitCount) throws IllegalArgumentException {
        if ((pIndex < 0) || ((pIndex + pBitCount) > Byte.SIZE)) {
            throw new IllegalArgumentException("pIndex out of bounds: " + pIndex);
        }

        if ((pBitIndex + pBitCount) > Byte.SIZE) {
            throw new IllegalArgumentException("pBitIndex out of bounds: " + pIndex);
        }

        if ((pBitCount < 0) || (pBitCount > Byte.SIZE)) {
            throw new IllegalArgumentException("pBitCount out of bounds: " + pIndex);
        }

        final int sizeMask = (0x01 << (pBitCount)) - 1;

        final int byteMask = (sizeMask << (Byte.SIZE - (pBitCount + pIndex))) ^ 0xFF;
        final int maskedByte = (pByte & byteMask) & 0xFF;

        final int bitMask = sizeMask << (Byte.SIZE - (pBitCount + pBitIndex));
        final int maskedBits = (pBits & bitMask) & 0xFF;

        final int shift = (pBitIndex - pIndex);
        final int shiftedBits;
        if (shift < 0) {
            shiftedBits = maskedBits >> -shift;
        } else {
            shiftedBits = maskedBits << shift;
        }

        final byte result = (byte) (maskedByte | shiftedBits);

        return result;
    }

    public static int calculateByteSize(final int pBitSize) {
        if (pBitSize < 0) {
            throw new IllegalArgumentException("pBitSize out of bounds: " + pBitSize);
        }

        if (BitVector.getIndexInByte(pBitSize) == 0) {
            return pBitSize / Byte.SIZE;
        } else {
            return 1 + (pBitSize / Byte.SIZE);
        }
    }

    public static int calculateLongSize(final int pBitSize) {
        if (pBitSize < 0) {
            throw new IllegalArgumentException("pBitSize out of bounds: " + pBitSize);
        }

        if (BitVector.getIndexInLong(pBitSize) == 0) {
            return pBitSize / Byte.SIZE;
        } else {
            return 1 + (pBitSize / Byte.SIZE);
        }
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
}
